using UnityEngine;
using System.Collections;

/// \class  gkAbyssalGiantSubmarine
/// \brief  The base for all the physic components
// it doesn't actually move the actor but update the acceleration and velocity
[AddComponentMenu("GK/Engine/Core/Physic/gkPhysicComponent")]
public class gkPhysicComponent : MonoBehaviour 
{
	/// The Event handler prototype
	public delegate void PhysicUpdateHandler();
	
	/// The initial velocity
    public Vector3 InitialLinearVelocity;
	
	/// Use the physic gravity
    public bool useGravity;
	
	/// The friction
    public float Friction;
	
	/// The maximum linear velocity
    public float MaximumLinearVelocity;

    /// The minimum linear velocity
    public float MinimumLinearVelocity = 0.001f;
	
	/// Is necessary to clamp the velocity
    public bool LimitVelocity;
	
	/// Called after angular velocity change
    public PhysicUpdateHandler OnSetAngularVelocity;
	
	/// Called after total linear velocity change
    public PhysicUpdateHandler OnSetLinearVelocity;
	
    /// The Acceleration
    protected Vector3 m_f3LinearAcceleration;

    /// The velocity
    protected Vector3 m_f3LinearVelocity;

    /// The angular velocity
    protected Vector3 m_f3AngularVelocity;
	
	/// The physic controller
	private gkPhysicController m_rPhysicController;
	
	/// \brief  Called just after the game begin to play
	void Awake()
	{
		// Set the initial velocity
		m_f3LinearVelocity = InitialLinearVelocity;
		
		// Register to the physic controller is there is one
		m_rPhysicController = GetComponent<gkPhysicController>();
		if(m_rPhysicController != null)
		{
			m_rPhysicController.RegisterPhysicComponent(this);
		}
	}
	
	/// \brief  Called each frame
    void Update()
    {
		if(useGravity)
		{
			UpdateGravity();
		}
		UpdateAngularMovement();
		UpdateLinearMovement();
    }
	
    /// \brief  Set the friction
    public void SetFriction(float a_fFriction)
    {
	    Friction = a_fFriction;
    }

    /// \brief  Add Force
    public void AddForce(Vector3 a_f3Force)
    {
	    m_f3LinearAcceleration += a_f3Force;
    }

    /// \brief  Set Velocity
    public void SetLinearVelocity(Vector3 a_f3NewLinearVelocity)
    {
	    m_f3LinearVelocity = a_f3NewLinearVelocity;
		NotifySetLinearVelocity();
    }

    /// \brief  Set Acceleration
    public void SetLinearAcceleration(Vector3 a_f3NewLinearAcceleration)
    {
	    m_f3LinearAcceleration = a_f3NewLinearAcceleration;
    }

    /// \brief  Set the maximum linear velocity
    public void SetMaximumLinearVelocity(float a_f3NewMaximumLinearVelocity)
    {
	    MaximumLinearVelocity = a_f3NewMaximumLinearVelocity;
	    if(MaximumLinearVelocity != 0)
	    {
		    LimitVelocity = true;
	    }
	    else
	    {
		    LimitVelocity = false;
	    }
    }

    /// \brief  Set Velocity
    public void SetAngularVelocity(Vector3 a_oNewAngularVelocity)
    {
	    m_f3AngularVelocity = a_oNewAngularVelocity;
		OnSetAngularVelocity();
    }

    /// \brief  Get Velocity
    public Vector3 GetLinearVelocity()
    {
	    return m_f3LinearVelocity;
    }

    /// \brief  Get angular velocity
    public Vector3 GetAngularVelocity()
    {
	    return m_f3AngularVelocity;
    }

    /// \brief  ResetVelocity
    public void ResetVelocity()
    {
		SetAngularVelocity(Vector3.zero);
	    SetLinearVelocity(Vector3.zero);
    }
	
	/// \brief  Update gravity
    void UpdateGravity()
    {
		m_f3LinearAcceleration += Physics.gravity;
	}
		
	/// \brief  Update angular movement
    void UpdateAngularMovement()
    {
	}
	
    /// \brief  Update linear movement
    void UpdateLinearMovement()
    {
		float fDeltaTimeInv;
			
	    // Compute the delta time inverse
		fDeltaTimeInv = 1.0f/Time.deltaTime;
			
		// Clamp the friction
		if(Friction > fDeltaTimeInv)
		{
			// Nullify the velocity
			Friction = fDeltaTimeInv;
		}
		
		// Add the friction force
		m_f3LinearAcceleration -= Friction * m_f3LinearVelocity;
		
		// Velocity integration
	   	m_f3LinearVelocity += m_f3LinearAcceleration * Time.deltaTime;
			
		// Clamp the velocity
		ClampLinearVelocity();
		
		// notify of the velocity update
		NotifySetLinearVelocity();

	    // Reset Acceleration
	    m_f3LinearAcceleration = Vector3.zero;
    }

    /// \brief  Damp the linear velocity
    void ClampLinearVelocity()
    {
	    float fCurrentLinearVelocityAmplitude;

	    fCurrentLinearVelocityAmplitude = m_f3LinearVelocity.magnitude;
    	
	    // Clamp max velocity if needed
	    if(LimitVelocity && fCurrentLinearVelocityAmplitude > MaximumLinearVelocity)
	    {
		    // Damp the total velocity
		    if(fCurrentLinearVelocityAmplitude != 0.0f)
		    {
			    m_f3LinearVelocity =  MaximumLinearVelocity * (m_f3LinearVelocity/fCurrentLinearVelocityAmplitude);
		    }
		    else
		    {
			    m_f3LinearVelocity = Vector3.zero;
		    }
	    }
    	
	    // Nullify the small velocities
	    if (fCurrentLinearVelocityAmplitude < MinimumLinearVelocity)
	    {
		    m_f3LinearVelocity = Vector3.zero;
	    }
    }
	
	/// \brief  Notify set linear velocity
	void NotifySetLinearVelocity()
	{
		if(OnSetLinearVelocity != null)
		{
			OnSetLinearVelocity();
		}
	}
}
